package defectsim.agents;

import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Vector2d;

import defectsim.Globals;
import defectsim.SimulationEnvironment;
import defectsim.Util;
import defectsim.agents.DefectFactory.DefectType;
import defectsim.integrator.Integrator;
import defectsim.integrator.IntegratorFactory;
import defectsim.integrator.IntegratorFactory.IntegrationType;
import defectsim.material.Material;
import defectsim.resources.AllForces;
import defectsim.resources.DefectResource.ForceType;
import repast.simphony.parameter.Parameters;
import repast.simphony.space.continuous.NdPoint;

public class EdgeDislocation extends Dislocation {

	private SimulationEnvironment environment = SimulationEnvironment.getInstance();

	private Material material;
	private double boxLength;
	private boolean shortRangeInteraction;
	private boolean midRangeInteraction;
	private List<Dislocation> longRangeNeighbours;
	private List<Dislocation> midRangeNeighbours;
	private List<Dislocation> shortRangeNeighbours;
	private List<PointDefect> pointDefects;
	private Vector2d effectiveForce = new Vector2d();
	private Vector2d netForce = new Vector2d();
	private double longPerceptionRadius;
	private double shortPerceptionRadius;
	private double midPerceptionRadius;
	private Vector2d externalForce = new Vector2d();
	private Vector2d frictionForce = new Vector2d();
	private Vector2d interactionForce = new Vector2d();
	private AllForces allForces = new AllForces();
	private Integrator integrator;
	private IntegrationType intType;
	private double pinningStress;
	private boolean climbAllowed;


	public EdgeDislocation(){
		Parameters p = environment.getParameters();
		longPerceptionRadius = (Double)p.getValue("maxcutoffradius");
		shortPerceptionRadius = (Double)p.getValue("annihilationlength") * environment.getMaterial().getBurgersVector() * Globals.NANOMETER_TO_MICROMETER; // in microns
		midPerceptionRadius = (Double)p.getValue("dipolelength") * environment.getMaterial().getBurgersVector() * Globals.NANOMETER_TO_MICROMETER; // in microns
		this.climbAllowed = (Boolean)p.getValue("climb");

		int type = (Integer)p.getValue("integrator");
		switch(type){
		case 0:
			intType = IntegrationType.Trapezoidal;
			break;
		case 1:
			intType = IntegrationType.VelocityVerlet;
			break;
		}
		this.integrator = IntegratorFactory.createIntegrator(intType);
		this.boxLength = (Double)p.getValue("spacelength");
		this.setDefectType(DefectType.EdgeDislocation);
		this.mobility = true; // mobile
		this.setDipole(false); // monopolar
		this.pinned = false;
		this.velocity = new Vector2d();
		this.distance = new Vector2d();
		this.acceleration = new Vector2d();
		this.setDeltaTime(environment.getTimeStep());
		this.midRangeInteraction = false; // no possible dipole formation
		this.shortRangeInteraction = false; // no short range interaction
		this.allForces = environment.getForces();
		this.material = environment.getMaterial();
		this.longRangeNeighbours = new ArrayList<Dislocation>();
		this.midRangeNeighbours = new ArrayList<Dislocation>();
		this.shortRangeNeighbours = new ArrayList<Dislocation>();
		this.pointDefects = new ArrayList<PointDefect>();
		this.effectiveForce = new Vector2d();
		this.netForce = new Vector2d();
	}

	public List<Dislocation> getLongRangeNeighbours(){
		return longRangeNeighbours;
	}

	public void setLongRangeNeighbours(List<Dislocation> neighbours){
		this.longRangeNeighbours = neighbours;
	}

	@Override
	public void percept() {
		//		if (environment.isNeighbourListUpdate() || environment.getCurrentTick() < 5) {
		if (!longRangeNeighbours.isEmpty()) 
			longRangeNeighbours.clear();
		if (!midRangeNeighbours.isEmpty())
			midRangeNeighbours.clear();
		if (!shortRangeNeighbours.isEmpty())
			shortRangeNeighbours.clear();
		if (!pointDefects.isEmpty())
			pointDefects.clear();
		environment.getNeighbours(this, longRangeNeighbours, midRangeNeighbours, 
				shortRangeNeighbours, pointDefects, longPerceptionRadius, midPerceptionRadius, 
				shortPerceptionRadius);
		//		}
		externalForce = allForces.getForce(ForceType.External).compute(this);
		frictionForce = allForces.getForce(ForceType.Friction).compute(this);
		interactionForce = allForces.getForce(ForceType.Interaction).compute(this);

		double b = this.getUnitBurgersVector() * material.getBurgersVector();
		effectiveForce.add(externalForce, interactionForce);
		if (effectiveForce.x > 0.0)
			netForce.sub(effectiveForce, frictionForce);
		else
			netForce.add(effectiveForce, frictionForce);

		//		this.setEffectiveStress(new Vector2d(effectiveForce.x / material.getBurgersVector(),
		//				effectiveForce.y / material.getBurgersVector()));
		this.setEffectiveStress(new Vector2d(effectiveForce.x / b,
				effectiveForce.y / b));
		
		this.setNetStress(new Vector2d(netForce.x / b,
				netForce.y / b));


		pinningStress = material.getShearModulus() / (2 * Math.PI * 2630.0 * b);
		
	}

	@Override
	public void mentalUpdate(){
		// If there is at least one dislocation in the vicinity of this dislocation 
		// it encounters short range interaction, e.g. annihilation or junction formation
		if (shortRangeNeighbours != null) shortRangeInteraction = true;

		// If there is at least one dislocation or point defect in the mid range of this dislocation 
		// there is a possibility for interaction. 
		if (midRangeNeighbours != null || pointDefects != null) midRangeInteraction = true;

		// Check whether the dislocation is mobile or not
		if (Math.abs(effectiveForce.x) > frictionForce.x){
			this.mobility = true;
			// Destruct the dipole formation if exists
//			if (this.isDipole()) {
//				this.setDipole(false);
//				if (this.getDipoleMate() != null){
//					this.getDipoleMate().setDipole(false);
//					this.setDipoleMate(null);
//				}
//			}
		}
		else 
			this.mobility = false;
		
		// Check whether dipole conditions are still valid	
		if (this.isDipole()) {
			Dislocation mate = this.getDipoleMate();
			NdPoint p2 = environment.getLocation(mate);
			NdPoint p1 = environment.getLocation(this);
			// check whether the dislocation is annihilated before 
			if ((p2 != null) && 
					(mate.getUnitBurgersVector() == -1.0 * this.getUnitBurgersVector()) &&
					mate.isMobile()){
				double dist = Util.calculateMICDistance(p1, p2, boxLength) * Globals.MICROMETER_TO_NANOMETER; // in nm
				double teta = Util.calculateMICAngle(p1, p2, boxLength) * 180 / Math.PI; // in degrees
				if (teta > 180) 
					teta -= 180;
				boolean inRange = false;
				if (teta > 25.0 && teta < 65.0)
					inRange = true;
				if (!(dist > shortPerceptionRadius && inRange)){
					// Destruct the dipole formation 
					this.setDipole(false);
					if (this.getDipoleMate() != null){
						this.getDipoleMate().setDipole(false);
						this.setDipoleMate(null);
					}
				}
			}
			else {
				// Destruct the dipole formation
				this.setDipole(false);
				if (this.getDipoleMate() != null){
					this.getDipoleMate().setDipole(false);
					this.setDipoleMate(null);
				}
			}
			
		}
	}



	@Override
	public void act() {
		boolean annihilated = false;
		if (shortRangeInteraction && !this.isPinned()){
			// check for annihilation
			for (Dislocation d : shortRangeNeighbours) {
				double dir = d.getDirection();
				double uBurgersVec = d.getUnitBurgersVector();
				if (dir == this.getDirection() && !this.isPinned()) 
					if (uBurgersVec == -1.0 * this.getUnitBurgersVector()){
						environment.annihilate(d);
						environment.annihilate(this);
						annihilated = true;
					}
					else{
						this.setUnitBurgersVector(this.getUnitBurgersVector() + d.getUnitBurgersVector());
						environment.annihilate(d);
					}
				else {
					if (Math.abs(this.getDirection() - dir) > 0 && Math.abs(this.getNetStress().x) < pinningStress) {
						this.setPinned();
						d.setPinned();
						this.velocity = new Vector2d();
						this.distance = new Vector2d();
						this.acceleration = new Vector2d();
						System.out.println(this.getNetStress().x + " " + pinningStress);
					}
				}
			}
		}
		if (midRangeInteraction && !annihilated){
			NdPoint p1 = environment.getLocation(this);
			// check for dipole formation
			if (!this.isDipole() && this.isMobile()){
				for (Dislocation dislocation : midRangeNeighbours) {
					if(!dislocation.isDipole()){
						NdPoint p2 = environment.getLocation(dislocation);
						// check whether the dislocation is annihilated before 
						if ((p2 != null) && 
								(dislocation.getUnitBurgersVector() == -1.0 * this.getUnitBurgersVector()) &&
								dislocation.isMobile()){
							double dist = Util.calculateMICDistance(p1, p2, boxLength) * Globals.MICROMETER_TO_NANOMETER; // in nm
							double teta = Util.calculateMICAngle(p1, p2, boxLength) * 180 / Math.PI; // in degrees
							if (teta > 180) 
								teta -= 180;
							boolean inRange = false;
							if (teta > 25.0 && teta < 65.0)
								inRange = true;
							if (dist > shortPerceptionRadius && inRange ){
								this.setDipole(true);
								dislocation.setDipole(true);
								this.setDipoleMate(dislocation);
								dislocation.setDipoleMate(this);
								break;
							}
						}
					}
				}
			}

			for (PointDefect pointDefect : pointDefects){
				NdPoint p2 = environment.getLocation(pointDefect);
				double dist = Util.calculateMICDistance(p1, p2, boxLength); 
				double teta = Util.calculateMICAngle(p1, p2, boxLength) * 180 / Math.PI; // in degrees
				//				System.out.println("Point defect at " + teta);
				boolean stop = false;
				double p = 45.0;
				if (teta > -p && teta < p && this.velocity.x >= 0.0)
					if (dist <= pointDefect.getPointDefectSize() / 2)
						stop = true;
				if (teta > (180.0 - p) && teta < (180.0 + p) && this.velocity.x <= 0.0)
					if (dist <= pointDefect.getPointDefectSize() / 2)
						stop = true;
				if (stop){
					//					System.out.println("stopped by point defect!");
					this.mobility = false; 
					this.velocity = new Vector2d();
					this.distance = new Vector2d();
					this.acceleration = new Vector2d();
				}
			}

		}

		if (this.isMobile() && !annihilated && !this.pinned){
			integrator.integrate((Dislocation)this, environment, netForce, allForces);
			this.velocity = integrator.getVelocity();
			this.distance = integrator.getDistance();
			this.acceleration = integrator.getAcceleration();

			if (this.climbAllowed){
//				double dr = Math.sqrt(this.distance.x * this.distance.x + this.distance.y * this.distance.y);
//				double alfa = Util.calculateAngle(this.distance.x, this.distance.y);
//				environment.moveByVector(this, dr, Math.toRadians(this.getDirection()) + alfa);
				environment.moveByVector(this, this.getDistance().x, Math.toRadians(this.getDirection()));
				environment.moveByVector(this, this.getDistance().y, Math.toRadians(this.getDirection() + 90.0));
			}
			else 
				environment.moveByVector(this, this.getDistance().x, Math.toRadians(this.getDirection()));
		}
		else {
			this.mobility = false; 
			this.velocity = new Vector2d();
			this.distance = new Vector2d();
			this.acceleration = new Vector2d();
		}

	}


	public int isEdgeDislocation(){
		return 1;
	}

}
